package cc.shacocloud.mirage.loader.jar;

import cc.shacocloud.mirage.loader.data.RandomAccessData;
import org.jetbrains.annotations.NotNull;

import java.io.IOException;

/**
 * zip文件的结尾记录对象
 *
 * @see <a href="https://en.wikipedia.org/wiki/Zip_%28file_format%29">Zip File Format</a>
 */
class CentralDirectoryEndRecord {
    
    private static final int MINIMUM_SIZE = 22;
    
    private static final int MAXIMUM_COMMENT_LENGTH = 0xFFFF;
    
    private static final int MAXIMUM_SIZE = MINIMUM_SIZE + MAXIMUM_COMMENT_LENGTH;
    
    private static final int SIGNATURE = 0x06054b50;
    
    private static final int COMMENT_LENGTH_OFFSET = 20;
    
    private static final int READ_BLOCK_SIZE = 256;
    
    private final Zip64End zip64End;
    
    private byte[] block;
    
    private int offset;
    
    private int size;
    
    CentralDirectoryEndRecord(RandomAccessData data) throws IOException {
        this.block = createBlockFromEndOfData(data, READ_BLOCK_SIZE);
        this.size = MINIMUM_SIZE;
        this.offset = this.block.length - this.size;
        while (!isValid()) {
            this.size++;
            if (this.size > this.block.length) {
                if (this.size >= MAXIMUM_SIZE || this.size > data.getSize()) {
                    throw new IOException("读取 " + this.size + " 个字节后找不到 ZIP 中央目录记录！");
                }
                this.block = createBlockFromEndOfData(data, this.size + READ_BLOCK_SIZE);
            }
            this.offset = this.block.length - this.size;
        }
        long startOfCentralDirectoryEndRecord = data.getSize() - this.size;
        Zip64Locator zip64Locator = Zip64Locator.find(data, startOfCentralDirectoryEndRecord);
        this.zip64End = (zip64Locator != null) ? new Zip64End(data, zip64Locator) : null;
    }
    
    private byte[] createBlockFromEndOfData(@NotNull RandomAccessData data, int size) throws IOException {
        int length = (int) Math.min(data.getSize(), size);
        return data.read(data.getSize() - length, length);
    }
    
    private boolean isValid() {
        if (this.block.length < MINIMUM_SIZE || Bytes.littleEndianValue(this.block, this.offset, 4) != SIGNATURE) {
            return false;
        }
        // 总大小必须是结构大小+注释
        long commentLength = Bytes.littleEndianValue(this.block, this.offset + COMMENT_LENGTH_OFFSET, 2);
        return this.size == MINIMUM_SIZE + commentLength;
    }
    
    long getStartOfArchive(@NotNull RandomAccessData data) {
        long length = Bytes.littleEndianValue(this.block, this.offset + 12, 4);
        long specifiedOffset = (this.zip64End != null) ? this.zip64End.centralDirectoryOffset
                : Bytes.littleEndianValue(this.block, this.offset + 16, 4);
        long zip64EndSize = (this.zip64End != null) ? this.zip64End.getSize() : 0L;
        int zip64LocSize = (this.zip64End != null) ? Zip64Locator.ZIP64_LOCSIZE : 0;
        long actualOffset = data.getSize() - this.size - length - zip64EndSize - zip64LocSize;
        return actualOffset - specifiedOffset;
    }
    
    /**
     * 根据此记录中指示的偏移量返回目录的字节数。
     */
    RandomAccessData getCentralDirectory(RandomAccessData data) {
        if (this.zip64End != null) {
            return this.zip64End.getCentralDirectory(data);
        }
        long offset = Bytes.littleEndianValue(this.block, this.offset + 16, 4);
        long length = Bytes.littleEndianValue(this.block, this.offset + 12, 4);
        return data.getSubsection(offset, length);
    }
    
    /**
     * 返回文件中的 ZIP 条目数
     */
    int getNumberOfRecords() {
        if (this.zip64End != null) {
            return this.zip64End.getNumberOfRecords();
        }
        long numberOfRecords = Bytes.littleEndianValue(this.block, this.offset + 10, 2);
        return (int) numberOfRecords;
    }
    
    String getComment() {
        int commentLength = (int) Bytes.littleEndianValue(this.block, this.offset + COMMENT_LENGTH_OFFSET, 2);
        AsciiBytes comment = new AsciiBytes(this.block, this.offset + COMMENT_LENGTH_OFFSET + 2, commentLength);
        return comment.toString();
    }
    
    boolean isZip64() {
        return this.zip64End != null;
    }
    
    /**
     * 中央目录记录的 Zip64 结尾。
     *
     * @see <a href="https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT">Zip64 规范 4.3.14 章</a>
     */
    private static final class Zip64End {
        
        // 条目总数
        private static final int ZIP64_ENDTOT = 32;
        
        // 中央目录大小（以字节为单位）
        private static final int ZIP64_ENDSIZ = 40;
        
        // 第一个 CEN 标头的偏移量
        private static final int ZIP64_ENDOFF = 48;
        
        private final Zip64Locator locator;
        
        private final long centralDirectoryOffset;
        
        private final long centralDirectoryLength;
        
        private final int numberOfRecords;
        
        private Zip64End(@NotNull RandomAccessData data, @NotNull Zip64Locator locator) throws IOException {
            this.locator = locator;
            byte[] block = data.read(locator.getZip64EndOffset(), 56);
            this.centralDirectoryOffset = Bytes.littleEndianValue(block, ZIP64_ENDOFF, 8);
            this.centralDirectoryLength = Bytes.littleEndianValue(block, ZIP64_ENDSIZ, 8);
            this.numberOfRecords = (int) Bytes.littleEndianValue(block, ZIP64_ENDTOT, 8);
        }
        
        /**
         * 返回此 zip 64 末尾的中央目录记录的大小
         */
        private long getSize() {
            return this.locator.getZip64EndSize();
        }
        
        /**
         * 根据此记录中指示的偏移量返回中央目录的字节数
         */
        private RandomAccessData getCentralDirectory(@NotNull RandomAccessData data) {
            return data.getSubsection(this.centralDirectoryOffset, this.centralDirectoryLength);
        }
        
        /**
         * 返回 zip64 存档中的条目数
         */
        private int getNumberOfRecords() {
            return this.numberOfRecords;
        }
        
    }
    
    /**
     * A Zip64 end of central directory locator.
     *
     * @see <a href="https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT">Zip64 规范 4.3.15 章</a>
     */
    private static final class Zip64Locator {
        
        static final int SIGNATURE = 0x07064b50;
        
        static final int ZIP64_LOCSIZE = 20;
        
        static final int ZIP64_LOCOFF = 8;
        
        private final long zip64EndOffset;
        
        private final long offset;
        
        private Zip64Locator(long offset, byte[] block) {
            this.offset = offset;
            this.zip64EndOffset = Bytes.littleEndianValue(block, ZIP64_LOCOFF, 8);
        }
        
        private static Zip64Locator find(RandomAccessData data, long centralDirectoryEndOffset) throws IOException {
            long offset = centralDirectoryEndOffset - ZIP64_LOCSIZE;
            if (offset >= 0) {
                byte[] block = data.read(offset, ZIP64_LOCSIZE);
                if (Bytes.littleEndianValue(block, 0, 4) == SIGNATURE) {
                    return new Zip64Locator(offset, block);
                }
            }
            return null;
        }
        
        /**
         * 返回此 zip64 结束定位器找到的 zip 64 结束记录的大小
         */
        private long getZip64EndSize() {
            return this.offset - this.zip64EndOffset;
        }
        
        /**
         * 返回偏移量以找到 {@link Zip64End}
         */
        private long getZip64EndOffset() {
            return this.zip64EndOffset;
        }
        
    }
    
}
